/**
 * 
 */
package shapes;

import java.awt.Color;

import util.RenderMatrix;

/**
 * @author Tarl
 *
 */
public abstract class UnitShape {

	/**
	 * Base Line Algorithm for comparison, standard DDA algorithm. 
	 */
	public static final int DDA = 1;
	
	/**
	 * Pixel Search Algorithm. 
	 */
	public static final int PSA = 2;

	/**
	 * Draws a line between two points in screen coordinates.
	 * @param vector1 the start of the line in the form of vector = {x, y, z, w}.
	 * @param vector2 the end of the line in the form of vector = {x, y, z, w}.
	 * @return All the pixels between the points in the form of array[i] = {x, y, z, w};
	 */
	protected double[][] pixelsBetweenPoints(final double[] vector1, final double[] vector2, 
											 final int algo_type) {
		
		switch(algo_type) {
		case DDA: return dda(vector1, vector2);
		//case PSA: return psa(vector1, vector2);
		default: return null;
		}
	}
	
	/**
	 * @param vector1
	 * @param vector2
	 * @return
	 */
	private double[][] dda(final double[] vector1, final double[] vector2) {
		double dx; double dy; // delta_x and delta_y.
		double x; double y; double z1;   // x and y
		double slope = (vector2[1] - vector1[1]) / (vector2[0] - vector1[0]); // slope
		final int size; // the size of the array holding the pixel data.
		
		double deltaZ; 
		double z2;
		// if the slope is a greater than positive 1 or greater than -1
		// dy = 1 dx = 1/slope.
		if (Math.abs(slope) > 1) {//x=my+b
			// y1 < y2 use y1
			if (vector1[1] < vector2[1]) {//vec1 is closer to y axis 
				// if x1 > x2 x = x1 - 1; else x = x1
				x = vector1[0];
				y = vector1[1];
				z1 = vector1[2];
				z2 = vector2[2];
			} else {
				// if x2 > x1 x = x2 - 1; else x = x2
				x = vector2[0];
				y = vector2[1];
				z1 = vector2[2];
				z2 = vector1[2];
			}
			// get size and assign dy and dx.
			size = Math.abs((int) Math.round(vector1[1] - vector2[1]));
			dy = 1;
			dx = 1 / slope;
			
		// else slope is less than |1| therefore standard dx = 1 dy = slope
		} else {//y=mx+b
			if (vector1[0] < vector2[0]) {
				// if y1 > y2 y = y1 - 1; else y = y1
				x = vector1[0];
				y = vector1[1];
				z1 = vector1[2];
				z2 = vector2[2];
			} else {
				// if y2 > y1 x = y2 - 1; else y = y2
				x = vector2[0];
				y = vector2[1];
				z1 = vector2[2];
				z2 = vector1[2];
			}
			// get size and assign dy and dx.
			size = Math.abs((int) Math.round(vector1[0] - vector2[0]));
			dx = 1;
			dy = slope;
		}

		deltaZ =  (z2 - z1) / (size + 1); // calcuate deltaZ
		
		double[][] pixel_array = new double[size + 1][4];
		// get the pixels between the points.
		////System.out.println("DDA- Z1:" + z + " Z2" + zMax + " deltaZ:" + deltaZ + " size:" + size);
		for (int i = 0; i <= size; i++) { 
			pixel_array[i][0] = Math.round(x);
			pixel_array[i][1] = Math.round(y);
			pixel_array[i][2] = z1;
			pixel_array[i][3] = 1; 
			x += dx;
			y += dy;
			z1 += deltaZ;
			////System.out.println("x:" + pixel_array[i][0] + " y:" + pixel_array[i][1] + " z:" + pixel_array[i][2]);
			addToScan(pixel_array[i][0], pixel_array[i][1], pixel_array[i][2]);
		}
		
		return pixel_array;
	}

	/**
	 * Dummy implementation needs to be overridden to produce 
	 * something useful.
	 * @return The pixels that make up the shape in the form of array[i] = {x, y, z}.
	 */
	public double[][] pixels() {
		final double[][] array = {{0, 0, 0}};
		return array;
	}
	
	/**
	 * @param vector
	 * @param transMatrix
	 * @return
	 */
	protected double[] transformWorld(double[] vector, final RenderMatrix transMatrix) {
		if(transMatrix == null) { 
			return new double[]{vector[0] / vector[3], vector[1] / vector[3], vector[2] / vector[3], 1}; 
		}		
		
		return transMatrix.multiply(vector);
	}
	
	protected void convertToScreen(double[] point, final int width, final int height) {
		point[0] = ((point[0] + 1) / 2) * (width - 1);
		point[1] = ((point[1] + 1) / 2) * (height - 1); 
	}
	
	public void addToScan(final double pixel_array, final double pixel_array2, final double pixel_array3) {	}
	
	public abstract void getPoints(final int width, final int height, final PaintPanel458 canvas,
								   final RenderMatrix itm);
}//, CURRENT_PERSPECTIVE, LOOK_AT, interactiveMatrix

class Tri extends UnitShape {
	final double[] vectorA;
	final double[] vectorB;
	final double[] vectorC;
	
	RenderMatrix trans_matrix;
	final Color rgb;
	private double[][] scanLine_array = null;
	
	public Tri(final double[] vector1, final double[] vector2, final double[] vector3, 
			   final RenderMatrix CTM, final Color color){
		vectorA = vector1;
		vectorB = vector2;
		vectorC = vector3;
		trans_matrix = CTM;
		rgb = color;
	}

	@Override
	public void getPoints(int width, int height, PaintPanel458 canvas,
				final RenderMatrix itm) {
		scanLine_array = new double[height][4];	
		for (int i = 0; i < height; i++) {
			scanLine_array[i][0] = Integer.MIN_VALUE;
			scanLine_array[i][1] = Integer.MIN_VALUE;
			scanLine_array[i][2] = Integer.MIN_VALUE;
			scanLine_array[i][3] = Integer.MIN_VALUE;
		}
		final RenderMatrix matrix = itm.multiply(trans_matrix);
		//System.out.println("ITM\n" + itm.toString());
		//System.out.println("Trans\n" + trans_matrix.toString());
		//System.out.println("itm * trans\n" + matrix.toString());
		
		final double[] pointA = transformWorld(vectorA, matrix); //convert from 4d to 3d
		final double[] pointB = transformWorld(vectorB, matrix);
		final double[] pointC = transformWorld(vectorC, matrix);
		
		convertToScreen(pointA, width, height);//convert from 4d to 3d
		convertToScreen(pointB, width, height);
		convertToScreen(pointC, width, height);
		
		double[][] pixels1 = pixelsBetweenPoints(pointB, pointA, DDA);
		double[][] pixels2 = pixelsBetweenPoints(pointB, pointC, DDA);
		double[][] pixels3 = pixelsBetweenPoints(pointC, pointA, DDA);
		
		// min_max array is min in index 0 and max in index 1.
		// assign max to min and min to max upon initialization.
		final int[] min_max = {height - 1, 0};
		int maxX = 0;
		
		// draw outline
		lineToCanvas(pixels1, min_max, canvas);
		lineToCanvas(pixels2, min_max, canvas);
		lineToCanvas(pixels3, min_max, canvas);
		
		// fill in the triangle
		double deltaZ;
		min_max[0] = Math.max(min_max[0], 0);
		min_max[1] = Math.min(min_max[1], scanLine_array.length);
		for (int i = min_max[0]; i < min_max[1]; i++ ) {
			maxX = (int) scanLine_array[i][2];
			deltaZ = (scanLine_array[i][3] - scanLine_array[i][1]) / (scanLine_array[i][2] - scanLine_array[i][0] + 1); 
			for (int j = (int) scanLine_array[i][0]; j <= maxX; j++) {
				if (canvas.checkZBuffer(j, i, scanLine_array[i][1] + deltaZ)) {
					canvas.drawPixel(j, i, rgb.getRed(), rgb.getGreen(), rgb.getBlue());
				}
				scanLine_array[i][1] = scanLine_array[i][1] + deltaZ;
			}
		}
	}
	
	/**
	 * Draws a line to a canvas
	 * @param pixels The pixels to draw
	 * @param min_max The min and max array used to return the min and max y values.
	 * @param canvas The panel to paint to.
	 */
	private void lineToCanvas(double[][] pixels, int[] min_max, final PaintPanel458 canvas) {
		for (int i = 0; i < pixels.length; i++) {
			min_max[0] = Math.min((int) pixels[i][1], min_max[0]);
			min_max[1] = Math.max(min_max[1], (int) pixels[i][1]);
			if (canvas.checkZBuffer((int) pixels[i][0], (int)pixels[i][1], pixels[i][2])) {
				canvas.drawPixel((int) pixels[i][0], (int) pixels[i][1], rgb.getRed(), rgb.getGreen(), rgb.getBlue());
			}
		}
		
	}

	
	@Override
	public void addToScan(final double x, final double y, final double z) {	
		if (scanLine_array == null || y < 0 || y > scanLine_array.length - 1) return;
		final int index = (int) y;
		double current_x = x;
		double current_z = z;
		
		// if index 0 is the default value assign current and return
		if (scanLine_array[index][0] == Integer.MIN_VALUE) {
			scanLine_array[index][0] = current_x;
			scanLine_array[index][1] = current_z;
			return;
		}
		
		// if index 0 is greater than x save the new current
		// then assign x and z.
		if (scanLine_array[index][0] > x) {
			current_x = scanLine_array[index][0];
			current_z = scanLine_array[index][1];
			scanLine_array[index][0] = x;
			scanLine_array[index][1] = z;
		} 
		
		// if index 2 is default assign current and return
		if (scanLine_array[index][2] == Integer.MIN_VALUE) {
			scanLine_array[index][2] = current_x;
			scanLine_array[index][3] = current_z;
			return;
		} 
		
		// if index 2 is less than x assign the new x and z
		if (scanLine_array[index][2] < x) {
			scanLine_array[index][2] = current_x;
			scanLine_array[index][3] = current_z;		
		}
	}
}

class Line extends UnitShape {
	
	final double[] vectorA;
	final double[] vectorB;
	
	RenderMatrix trans_matrix;
	final Color rgb;
	
	public Line(final double[] vector1, final double[] vector2, final RenderMatrix CTM, 
			    final Color color){
		vectorA = vector1;
		vectorB = vector2;
		trans_matrix = CTM;
		rgb = color;
	}

	@Override
	public void getPoints(int width, int height, PaintPanel458 canvas,
				final RenderMatrix itm) {

		final RenderMatrix matrix = itm.multiply(trans_matrix);	
		final double[] pointA = transformWorld(vectorA, matrix);
		final double[] pointB = transformWorld(vectorB, matrix);
		
		convertToScreen(pointA, width, height);
		convertToScreen(pointB, width, height);
		final double[][] pixels = pixelsBetweenPoints(pointB, pointA, DDA);
	
		for (int i = 0; i < pixels.length; i++) {
			if (pixels[i][2] == Double.NaN) { pixels[i][2] = 0; }
			if (canvas.checkZBuffer((int) pixels[i][0], (int)pixels[i][1], pixels[i][2])) {
				canvas.drawPixel((int) pixels[i][0], (int) pixels[i][1], rgb.getRed(), rgb.getGreen(), rgb.getBlue());
			}
		}
	}
}

/*private static double[][]psa(final double[] vector1, final double[] vector2) {

// get the start x and y
int x = (int)vector1[0];
int y = (int)vector1[1];

final int delta_x = (int) -(vector1[0] - vector2[0]);
final int delta_y = (int) -(vector1[1] - vector2[1]);

final int delta_xy = delta_x - delta_y;
//System.out.println("delta_x is " + delta_x + " delta_y is " + delta_y);

final int b = (int) (vector1[1] - vector1[0] * delta_y / delta_x);

int x_increm = delta_x < 0 ? -1 : (delta_x == 0 ? 0 : 1);
int y_increm = delta_y < 0 ? -1 : (delta_y == 0 ? 0 : 1);

// load with initial data
int x_prime = delta_y * x;
int y_prime = delta_x * (y - b);

int xy_current = x_prime - y_prime;
int x_current = xy_current;
int y_current = xy_current;

// start the array list for dynamic array functionality.
final ArrayList<double[]> result = new ArrayList<double[]>();
double[] temp;  // assign the first point.

//System.out.println("Getting pixels initial pixel x:" + x + " y:" + y);
//System.out.println("Last pixels should be (" + vector2[0] + ", " + vector2[1] + ")");

while (x != vector2[0] && y != vector2[1]) {
	
	temp = new double[]{x, y, 1, 1}; // assign the current point
	result.add(temp); // add the current point
	
	x_prime += delta_x; // with equation is increment by x only
	y_prime += delta_y; // with equation is increment by y only
	xy_current = x_prime + y_prime; // with equation is increment by x and y
	x_current = x_prime - y; // <-------- not right, rethink your approach.
	
	//System.out.println("x:" + x + " y:" + y);
	//System.out.println("x_plus:" + x_prime + " y_plus:" + y_prime + " xy_plus:" + xy_current);
	// x is the lowest
	if (x_current < y_current && x_current < xy_current) {
		// assign the new base point other vars
		y_prime -= delta_x; 
		// increment x
		x = x + x_increm;
	} else if (y_current < xy_current) {
		// assign new base point to other vars
		x_prime -= delta_y;
		// increment y
		y = y + y_increm;
	} else { // xy is the closets to zero.
		// incremnt x and y
		x = x + x_increm;
		y = y + y_increm;
	}
}

// finish the 
if (x != vector2[0]) {
	//System.out.println("X != X2 " + x + " != " + vector2[0]);
	while (x != vector2[0]) {
		temp = new double[]{x, y, 1, 1}; // assign the current point
		result.add(temp); // add the current point
		x += x_increm;
	}
	
}
// finish the y increments
if (y != vector2[1]) {

	//System.out.println("Y != Y2 " + y + " != " + vector2[1]);
	while (y != vector2[1]) {
		temp = new double[]{x, y, 1, 1}; // assign the current point
		result.add(temp); // add the current point
		y += y_increm;
	}
}

double[][] array = {{0,0,0,0}};
return result.toArray(array);  array; 
}
*/	
